/*
 * ESPRESSIF MIT License
 *
 * Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
 *
 * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
 * it is free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include <string.h>
#include "esp_log.h"
#include "i2c_bus.h"
#include "pcm5121.h"
#include "board_pins_config.h"

static const char *ES_TAG = "PCM5121_DRIVER";
static i2c_bus_handle_t i2c_handle;

#define ES_ASSERT(a, format, b, ...) \
    if ((a) != 0) { \
        ESP_LOGE(ES_TAG, format, ##__VA_ARGS__); \
        return b;\
    }

audio_hal_func_t AUDIO_CODEC_PCM5121_DEFAULT_HANDLE = {
    .audio_codec_initialize = pcm5121_init,
    .audio_codec_deinitialize = pcm5121_deinit,
    .audio_codec_ctrl = pcm5121_ctrl_state,
    .audio_codec_config_iface = pcm5121_config_i2s,
    .audio_codec_set_mute = pcm5121_set_voice_mute,
    .audio_codec_set_volume = pcm5121_set_voice_volume,
    .audio_codec_get_volume = pcm5121_get_voice_volume,
    .audio_hal_lock = NULL,
    .handle = NULL,
};

static esp_err_t pcm_write_reg(uint8_t slave_addr, uint8_t reg_add, uint8_t data)
{
    return i2c_bus_write_bytes(i2c_handle, slave_addr, &reg_add, sizeof(reg_add), &data, sizeof(data));
}

static esp_err_t pcm_read_reg(uint8_t reg_add, uint8_t *p_data)
{
    return i2c_bus_read_bytes(i2c_handle, PCM5121_ADDR, &reg_add, sizeof(reg_add), p_data, 1);
}

static int i2c_init()
{
    int res;
    i2c_config_t pcm_i2c_cfg = {
        .mode = I2C_MODE_MASTER,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = 100000
    };
    res = get_i2c_pins(I2C_NUM_0, &pcm_i2c_cfg);
    ES_ASSERT(res, "getting i2c pins error", -1);
    i2c_handle = i2c_bus_create(I2C_NUM_0, &pcm_i2c_cfg);
    return res;
}

void pcm5121_read_all()
{
    pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    for (int i = 0; i < PCM5121_PAGE0_7D; i++) {
        uint8_t reg = 0;
        pcm_read_reg(i, &reg);
        ets_printf("page0 %x: %x\n", i, reg);
    }
}

esp_err_t pcm5121_write_reg(uint8_t reg_add, uint8_t data)
{
    return pcm_write_reg(PCM5121_ADDR, reg_add, data);
}

/**
 * @brief Power Management
 *
 * @param mod:      if ES_POWER_CHIP, the whole chip including ADC and DAC is enabled
 * @param enable:   false to disable true to enable
 *
 * @return
 *     - (-1)  Error
 *     - (0)   Success
 */
esp_err_t pcm5121_start(pcm_module_t mode)
{
    esp_err_t res = ESP_OK;
    uint8_t prev_data = 0, data = 0;

    if (mode == ES_MODULE_DAC) {
        res |= pcm5121_set_voice_mute(false);
        ESP_LOGD(ES_TAG, "pcm5121_start default is mode:%d", mode);
    }

    return res;
}

/**
 * @brief Power Management
 *
 * @param mod:      if ES_POWER_CHIP, the whole chip including ADC and DAC is enabled
 * @param enable:   false to disable true to enable
 *
 * @return
 *     - (-1)  Error
 *     - (0)   Success
 */
esp_err_t pcm5121_stop(pcm_module_t mode)
{
    esp_err_t res = ESP_OK;
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    if (mode == ES_MODULE_DAC) {
        res |= pcm5121_set_voice_mute(true); 
    }

    return res;
}

esp_err_t pcm5121_deinit(void)
{
    int res = 0;
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_02, 1 << 4);  // Standby mode
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_01, 0x03);  // Reset modules,Reset mode registers
    i2c_bus_delete(i2c_handle);

    return res;
}

/**
 * @return
 *     - (-1)  Error
 *     - (0)   Success
 */
esp_err_t pcm5121_init(audio_hal_codec_config_t *cfg)
{
    int res = 0;

    res = i2c_init(); // ESP32 in master mode

    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0x00);//page 0 select
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_02, 0x10);//standby mode
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_01, 0x11);//reset reg
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_02, 0x00);//Normal operation
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_0D, 0x10);//The PLL reference clock is BCK
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_0E, 0x10);//PLL clock
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_25, 0x7D);//Ignore FS  BCK  SCK detection ......
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_3D, 100);//The Left digital volume is 24 dB to -103 dB in -0.5 dB step
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_3E, 100);//The  Right digital volume is 24 dB to -103 dB in -0.5 dB step
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_41, 0x07);//This bit controls the behavior of the auto mute upon zero sample detection

    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0x01);//page 1 select
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE1_05, 0x02);//XSMUTE  External Under Voltage Protection

    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0x00);//page 0 select

    /* enable pcm5121 PA */
    pcm5121_pa_power(true);
    ESP_LOGI(ES_TAG, "init,out:%02x", cfg->dac_output);
    return res;
}

/**
 * @brief Configure PCM5121 I2S format
 *
 * @param mode:           set ADC or DAC or all
 * @param bitPerSample:   see Es8388I2sFmt
 *
 * @return
 *     - (-1) Error
 *     - (0)  Success
 */
esp_err_t pcm5121_config_fmt(pcm_module_t mode, audio_hal_iface_format_t fmt)
{
    esp_err_t res = ESP_OK;
    uint8_t reg = 0;

    reg = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    if (mode == ES_MODULE_DAC) {
        res = pcm_read_reg(PCM5121_PAGE0_28, &reg);
        reg = reg & ~(0x03 << 4);
        switch(fmt)
        {
            case AUDIO_HAL_I2S_NORMAL:
            fmt = ES_I2S_NORMAL;                
            break;
            case AUDIO_HAL_I2S_LEFT:
            fmt = ES_I2S_LEFT;
            break;
            case AUDIO_HAL_I2S_RIGHT:
            fmt = ES_I2S_RIGHT;
            break;
            case AUDIO_HAL_I2S_DSP:
            fmt = ES_I2S_TDM_DSP;
            break;                    
        }
        res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_28, reg | (fmt << 4));
    }
    return res;
}

/**
 * @param volume: 0 ~ 100
 *
 * @return
 *     - (-1)  Error
 *     - (0)   Success
 */
esp_err_t pcm5121_set_voice_volume(int volume)
{
    esp_err_t res = ESP_OK;

    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    if (volume < 0)
        volume = 0;
    else if (volume > 100)
        volume = 100;

    volume /= 3;  
    volume = 100 - volume;    
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_3D, volume);
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_3E, volume);
    return res;
}

/**
 *
 * @return
 *           volume
 */
esp_err_t pcm5121_get_voice_volume(int *volume)
{
    esp_err_t res = ESP_OK;
    uint8_t reg = 0;

    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    res = pcm_read_reg(PCM5121_PAGE0_3D, &reg);
    if (res == ESP_FAIL) {
        *volume = 0;
    } else {
        *volume = 100 - reg;
        *volume /= 3;
    }
    return res;
}

/**
 * @brief Configure PCM5121 data sample bits
 *
 * @param mode:             set ADC or DAC or all
 * @param bitPerSample:   see BitsLength
 *
 * @return
 *     - (-1) Parameter error
 *     - (0)   Success
 */
esp_err_t pcm5121_set_bits_per_sample(pcm_module_t mode, pcm_bits_length_t bits_length)
{
    esp_err_t res = ESP_OK;
    uint8_t reg = 0;
    int bits = (int)bits_length;

    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    if (mode == ES_MODULE_DAC) {
        res = pcm_read_reg(PCM5121_PAGE0_28, &reg);
        reg = reg & 0xc7;
        res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_28, reg | bits);
    }
    return res;
}

/**
 * @brief Configure PCM5121 DAC mute or not. Basically you can use this function to mute the output or unmute
 *
 * @param enable: enable or disable
 *
 * @return
 *     - (-1) Parameter error
 *     - (0)   Success
 */
esp_err_t pcm5121_set_voice_mute(bool enable)
{
    esp_err_t res = ESP_OK;
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    res |= pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE0_03, (enable << 4) | (enable << 0));
    return res;
}

esp_err_t pcm5121_get_voice_mute(void)
{
    esp_err_t res = ESP_OK;
    uint8_t reg = 0;
    res = pcm_write_reg(PCM5121_ADDR, PCM5121_PAGE_SET_REG, 0);  // set page0
    res = pcm_read_reg(PCM5121_PAGE0_03, &reg);
    if (res == ESP_OK) {
        if(reg == ((1 << 4) | (1 << 0))){
            return 1;
        }else{
            return ESP_FAIL;
        }
    }
    return ESP_FAIL;
}

int pcm5121_ctrl_state(audio_hal_codec_mode_t mode, audio_hal_ctrl_t ctrl_state)
{
    int res = 0;
    int pcm_mode_t = 0;
    switch (mode) {
        case AUDIO_HAL_CODEC_MODE_DECODE:
            pcm_mode_t  = ES_MODULE_DAC;
            break;
        default:
            pcm_mode_t = ES_MODULE_DAC;
            ESP_LOGW(ES_TAG, "Codec mode not support, default is decode mode");
            break;
    }
    if (AUDIO_HAL_CTRL_STOP == ctrl_state) {
        res = pcm5121_stop(pcm_mode_t);
    } else {
        res = pcm5121_start(pcm_mode_t);
        ESP_LOGD(ES_TAG, "start default is decode mode:%d", pcm_mode_t);
    }
    return res;
}

esp_err_t pcm5121_config_i2s(audio_hal_codec_mode_t mode, audio_hal_codec_i2s_iface_t *iface)
{
    esp_err_t res = ESP_OK;
    int tmp = 0;
    res |= pcm5121_config_fmt(ES_MODULE_DAC, iface->fmt);
    if (iface->bits == AUDIO_HAL_BIT_LENGTH_16BITS) {
        tmp = BIT_LENGTH_16BITS;
    } else if (iface->bits == AUDIO_HAL_BIT_LENGTH_24BITS) {
        tmp = BIT_LENGTH_24BITS;
    } else {
        tmp = BIT_LENGTH_32BITS;
    }
    res |= pcm5121_set_bits_per_sample(ES_MODULE_DAC, tmp);
    return res;
}

void pcm5121_pa_power(bool enable)
{
    gpio_config_t  io_conf;
    memset(&io_conf, 0, sizeof(io_conf));
    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = BIT64(get_pa_enable_gpio());
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);
    if (enable) {
        gpio_set_level(get_pa_enable_gpio(), 1);
    } else {
        gpio_set_level(get_pa_enable_gpio(), 0);
    }
}
